﻿#define DO_RESIZE

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;

namespace Isotop.Text
{
	public sealed partial class LazyStringBuilder : VStringBase
	{
		internal struct CharSequence
		{
			public int Length;
			public char[] CharArray;
			public string String;

			public string GetAsString()
			{
				if (String != null)
					return String;
				return new String(CharArray, 0, Length);
			}
		}

		public sealed class DynArrList<T>
		{
			public DynArrList<T> Previous;
			public DynArrList<T> Next;
			public T[] Array;
			public int Index;

			public DynArrList() { }

			public DynArrList(int size)
			{
				Array = new T[size];
			}
		}

		const int DEFAULT_TEMP_SIZE = 16; //<- Critical to get right (high for low string lengths (1000), low for mixed(2))
		const double GROW_FACTOR = 1.2;
		double _dynArrGrowFactor = 5; //<!- Seems 2 is good for higher, 5 for lower

		DynArrList<CharSequence> _first;

		internal DynArrList<CharSequence> Strings = new DynArrList<CharSequence>(DEFAULT_TEMP_SIZE);

		int _length;
		int _curTempSize = DEFAULT_TEMP_SIZE;
		char[] _tempBuffer = new char[DEFAULT_TEMP_SIZE];
		int _templen = 0;
		int _noFlushed = 0;

		public LazyStringBuilder()
		{
			_first = Strings;
		}

		public LazyStringBuilder(string s) : this()
		{
			Append(s);
		}


		internal IEnumerable<DynArrList<CharSequence>> Parts
		{
			get
			{
				DynArrList<CharSequence> i = _first;
				do
				{
					yield return i;
					i = i.Next;
				} while (i != null);
			}
		}

		IEnumerable<CharSequence> Sequences
		{
			get
			{
				foreach (var part in Parts)
					foreach (var seq in part.Array)
						yield return seq;
			}
		}

		void Update()
		{
			Debug.WriteLine("Updating");

			var nArr = new DynArrList<CharSequence> { Array = Strings.Array, Next = Strings, Previous = Strings.Previous };
			if (Strings.Previous != null)
			{
				Strings.Previous.Next = nArr;
			}
			else
			{
				_first = nArr;
			}
			Strings.Previous = nArr;
			if(_dynArrGrowFactor > 1.5)
				_dynArrGrowFactor /= 1.2;
			var size = (int) (Strings.Array.Length * _dynArrGrowFactor);

			Strings.Array = new CharSequence[size];
			Strings.Index = 0;
		}

		public unsafe LazyStringBuilder Append(string s)
		{
			if (s != null)
			{
				var sLen = s.Length;
				if (sLen < 51)
				{
					if (sLen + _templen > _curTempSize)
					{
						if (sLen > _curTempSize)
						{
#if DEBUG
							Debug.WriteLine("Setting _curTempSize to " + sLen);
#endif
							_curTempSize = sLen; // +_templen;
							//_noFlushed = 0;
						}
						//else
							//_noFlushed++;
						FlushTempBuilder(true);
					}
					//else
						//_noFlushed--;

					if (sLen < 3)
					{
						if (sLen > 0)
						{
							_tempBuffer[_templen] = s[0];
							if (sLen > 1)
								_tempBuffer[_templen + 1] = s[1];
						}
					}
					else
					{
						fixed (char* dp = _tempBuffer)
							fixed (char* sp = s)
						     wstrcpy(dp + _templen, sp, sLen);
					}
					_templen += sLen;
				}
				else
				{
					FlushTempBuilder(false);
					if (Strings.Index == Strings.Array.Length)
						Update();
					Strings.Array[Strings.Index++] = new CharSequence { String = s, Length = sLen };
					_length += sLen;
				}
			}
			return this;
		}

		void FlushTempBuilder(bool possibleIncrease)
		{
			if (_templen != 0)
			{
				if (Strings.Index == Strings.Array.Length)
					Update();
				Strings.Array[Strings.Index++] = new CharSequence {CharArray = _tempBuffer, Length = _templen};

#if DO_RESIZE		
					if (possibleIncrease)
					{
						if(_curTempSize > 500)
							_curTempSize = (int) (_curTempSize*1.02);
						else
							_curTempSize = (int)(_curTempSize * 5);
					}
					else
					{
						_curTempSize = (int)(_curTempSize / GROW_FACTOR);
						   if (_curTempSize < DEFAULT_TEMP_SIZE)
						      _curTempSize = DEFAULT_TEMP_SIZE;
					}
	#if DEBUG
					Console.WriteLine("Increasing? " + possibleIncrease + " _curTempSize to " + _curTempSize);
	#endif
#endif
				
				//_templen = _curTempSize/2;
				//if (_noFlushed > 2)
				//{
				//   _curTempSize = (int)(_curTempSize * GROW_FACTOR);
				//   //Console.WriteLine(_curTempSize);
				//   _noFlushed = 0;
				//}
				//else if (_noFlushed < -2)
				//{
				//   _curTempSize = (int)(_curTempSize / GROW_FACTOR);
				//   if (_curTempSize < DEFAULT_TEMP_SIZE)
				//      _curTempSize = DEFAULT_TEMP_SIZE;
				//   _noFlushed = 0;
				//}

				//if(curTempSize > DEFAULT_TEMP_SIZE)
				//Console.WriteLine(curTempSize);
				_tempBuffer = new char[_curTempSize];
				_length += _templen;
				_templen = 0;
				//if (_tempBuilder.Length == 0)
				//   return;
				//_strings.Add(_tempBuilder.ToString());
				//_length += _tempBuilder.Length;
				//_tempBuilder.Clear();
			}
		}

		/*public static implicit operator string(LazyStringBuilder vstr) 
		{
		   return vstr.ToString();
		}*/
		/*
		public static implicit operator LazyStringBuilder(string str) 
		{
		   var vstr = new LazyStringBuilder();
		   vstr.Append(str);
		   return vstr;
		}*/

		/// <summary>
		/// Allocates a new string and copies all string data to it
		/// </summary>
		[SecurityCritical]
		public unsafe override string ToString()
		{
			FlushTempBuilder(false);
			var output = FastAllocateString(Length);
			fixed (char* dst = output)
			{
				char* curDst = dst;
				foreach (var part in Parts)
				{
					foreach (var item in part.Array)
					{
						var len = item.Length;
						if (item.CharArray != null)
						{
							fixed (char* src = item.CharArray)
								wstrcpy(curDst, src, item.Length);
						}
						else
						{
							fixed (char* src = item.String)
								wstrcpy(curDst, src, item.Length);
						}
						curDst += len;
					}
				}
			}
			return output;
		}


		public int Length
		{
			get
			{
				return _length;
			}
		}

		public void WriteTo(TextWriter tw)
		{
			foreach (var item in Sequences)
			{
				if (item.String != null)
					tw.Write(item.String);
				else
					tw.Write(item.CharArray, 0, item.Length);
			}
		}

		//public LazyStringBuilder Substring(int index)
		//{
		//   if (_strings.Count == 0 && index == 0)
		//      return string.Empty;

		//   int startAt = 0;
		//   int i;
		//   for (i = 0; i < _strings.Count; i++)
		//   {
		//      var len = _strings[i].Length;
		//      if (startAt + len > index)
		//         break;
		//      startAt += len;
		//   }
		//   var vstr = new LazyStringBuilder();
		//   var iStartAt = index - startAt;

		//   if (iStartAt == 0)
		//      vstr._strings.Add(_strings[i]);
		//   else
		//      vstr._strings.Add(_strings[i].Substring(index - startAt)); //Can throw
		//   for (i += 1; i < _strings.Count; i++)
		//      vstr._strings.Add(_strings[i]);
		//   return vstr;
		//}

		public void Clear()
		{
			Strings = _first = new DynArrList<CharSequence>(DEFAULT_TEMP_SIZE);
			_length = 0;
		}

		public LazyStringBuilder Remove(int startIndex, int length)
		{
			//TODO: Make faster implementation
			var thisAsString = ToString();
			Clear();
			Append(thisAsString.Substring(0, startIndex));
			return Append(thisAsString.Substring(startIndex + length));
		}

		public LazyStringBuilder Replace(char oldChar, char newChar)
		{
			//TODO: Make faster implementation
			var thisAsString = ToString();
			var replaced = thisAsString.Replace(oldChar, newChar);
			Clear();
			return Append(replaced);
		}

		public LazyStringBuilder Replace(string oldStr, string newStr)
		{
			//TODO: Make faster implementation
			var thisAsString = ToString();
			var replaced = thisAsString.Replace(oldStr, newStr);
			Clear();
			return Append(replaced);
		}
		
		public LazyStringBuilder AppendFormat(string format, params object[] args)
		{
			return Append(string.Format(format, args));
		}

		public LazyStringBuilder AppendLine()
		{
			return Append(Environment.NewLine);
		}

		public LazyStringBuilder AppendLine(string value)
		{
			Append(Environment.NewLine);
			return Append(value);
		}

		public LazyStringBuilder Append(object value)
		{
			return Append(value.ToString());
		}

		public LazyStringBuilder Append(char[] value)
		{
			//TODO: Can be optimized a little
			return Append(new String(value));
		}

		public LazyStringBuilder Insert(int index, string value)
		{
			//TODO: Optimize
			var thisAsString = ToString();
			var inserted = thisAsString.Insert(index, value);
			return new LazyStringBuilder(inserted);
		}

		public LazyStringBuilder Insert(int index, object value)
		{
			return Insert(index, value.ToString());
		}

		public LazyStringBuilder Insert(int index, char[] value)
		{
			//TODO: Can be optimized a little
			return Insert(index, new String(value));
		}
	}
}